package falcon.client;

import java.util.Iterator;
import java.util.LinkedList;

import falcon.common.Errno;
import falcon.common.Log;
import falcon.protocol.Packet;
import falcon.protocol.Protocol;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public final class FalconClient extends SimpleChannelInboundHandler<ByteBuf> {
	
	public interface Callback {
		public void onComplete(int errno, Packet packet);
	}
	
	public interface RecycleListener {
		public void recycle();
	}

	public FalconClient() {
		m_id = hashCode();
		m_linker = new LinkedList<Info>();
		m_protocol = new Protocol();
		m_ctx = null;
	}
	
	public void bindListener(RecycleListener listener) {
		m_listener = listener;
	}
	
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf buf) throws Exception {
		int errno = m_protocol.decode(buf);
		if (errno != Errno.SUCCESS) {
			Log.log("decode ResultPacket error;id=%d;errno=%d;", m_id, errno);
			close();
			Iterator<Info> iter = m_linker.iterator();
			while (iter.hasNext()) {
				Info info = iter.next();
				iter.remove();
				try {
					info.callback.onComplete(errno, null);
				} catch (Exception e) {
					Log.log(e, "onComplete error;id=%x", info.id);
				}
			}
			return;
		}
		Packet packet;
		long now = System.currentTimeMillis();
		while ((packet = m_protocol.get()) != null) {
			Iterator<Info> iter = m_linker.iterator();
			while (iter.hasNext()) {
				Info info = iter.next();
				try {
					if (info.expire > 0 && now - info.timestamp > info.expire) {
						Log.log("request timeout;id=%x;expire=%d", info.id, info.expire);
						iter.remove();
						info.callback.onComplete(Errno.NET_RECV_TIMEOUT, null);
					} else if (packet.id == info.id) {
						iter.remove();
						info.callback.onComplete(Errno.SUCCESS, packet);
					}
				} catch (Exception e) {
					Log.log(e, "onComplete error;id=%x", info.id);
				}
			}
		}
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		m_ctx = ctx;
		Log.log("id=%d", m_id);
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		m_ctx = null;
		Log.log("id=%d", m_id);
		m_listener.recycle();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable thr) throws Exception {
		m_ctx = null;
		Log.log(thr, "id=%d", m_id);
	}
	
	public void sendPacket(int expire, Packet packet, Callback callback) {
		if (packet == null || callback == null) {
			return;
		}
		if (isClose()) {
			return;
		}
		int id = packet.hashCode();
		packet.id = id;
		ByteBuf buf = m_protocol.encode(packet);
		if (buf == null) {
			return;
		}
		Info info = new Info();
		info.id = id;
		info.expire = expire;
		info.timestamp = System.currentTimeMillis();
		info.callback = callback;
		synchronized (this) {
			m_linker.addLast(info);
			m_ctx.writeAndFlush(buf);
		}
	}
	
	public synchronized boolean isClose() {
		return m_ctx == null || !m_ctx.channel().isActive();
	}
	
	public synchronized void close() {
		if (!isClose()) {
			m_ctx.close();
			m_ctx = null;
		}
	}
	
	private class Info {
		public int id;
		public int expire;
		public long timestamp;
		public Callback callback;
	}

	private int m_id;
	private LinkedList<Info> m_linker;
	private Protocol m_protocol;
	private ChannelHandlerContext m_ctx;
	private RecycleListener m_listener;
}
